package hmac

import (
	"crypto/hmac"
	"crypto/sha256"
	"fmt"
	"strconv"
	"strings"
	"time"
)

const (
	AceGatewayProxyPrefix     = "Ace-Gateway-Proxy-"
	AceGatewayProxyAuthKey    = AceGatewayProxyPrefix + "Auth"
	AceGatewayProxyVersionKey = AceGatewayProxyPrefix + "Version"

	AceGatewayProxyVersionV2 = "v2"
	signKeyDataFmt           = "%s/%s/%d/%d/%s/%s/%s/%s"
	authFmt                  = signKeyDataFmt + "/%s"
	signKeyDataV2Fmt         = "%s/%s/%d/%d/%s/%s/%s/%s/%t"
	authV2Fmt                = signKeyDataV2Fmt + "/%s"
	signKeyDataV3Fmt         = "%s/%s/%d/%d/%s/%s/%s/%s/%t"
	authV3Fmt                = signKeyDataV2Fmt + "/%s"
	authV4Fmt                = "%s/%s/%s/%d/%d/%s/%s/%s/%s/%t/%s"
)

type ProxyMode string

const (
	ProxyModeRoute  ProxyMode = "route"
	ProxyModeDirect ProxyMode = "direct"
)

type version string

const (
	// deprecated, use VersionV2/VersionV3 instead
	// auth payload,proxyValue,proxyScheme,proxyHost
	VersionV1 version = "v1"
	//auth payload,method,path,proxyValue,proxyScheme,proxyHost
	VersionV2 version = "v2"
	//auth proxyValue,proxyScheme,proxyHost
	VersionV3 version = "v3"
	VersionV4 version = "v4"
)

func SignAceGatewayProxyAuthV3(proxyMode ProxyMode,
	proxyValue string,
	proxyScheme string,
	proxyHost string,
	isUnderlay bool,
	accessKey, secretKey string,
	expiration int64) (string, error) {

	signData := []byte("")

	authVersion := VersionV3
	timestamp := time.Now().Unix()
	signKeyDataSlice := make([]string, 0, 11)
	signKeyDataSlice = append(signKeyDataSlice, string(authVersion))
	signKeyDataSlice = append(signKeyDataSlice, accessKey)
	signKeyDataSlice = append(signKeyDataSlice, strconv.FormatInt(timestamp, 10))
	signKeyDataSlice = append(signKeyDataSlice, strconv.FormatInt(expiration, 10))
	signKeyDataSlice = append(signKeyDataSlice, string(proxyMode))
	signKeyDataSlice = append(signKeyDataSlice, proxyValue)
	signKeyDataSlice = append(signKeyDataSlice, proxyScheme)
	signKeyDataSlice = append(signKeyDataSlice, proxyHost)
	signKeyDataSlice = append(signKeyDataSlice, strconv.FormatBool(isUnderlay))
	signKeyData := strings.Join(signKeyDataSlice, "\n")

	signKey := sha256HMAC([]byte(secretKey), []byte(signKeyData))
	signature := sha256HMAC(signKey, signData)
	auth := fmt.Sprintf(authV3Fmt, authVersion, accessKey, timestamp, expiration, proxyMode, proxyValue, proxyScheme, proxyHost, isUnderlay, signature)
	return auth, nil
}

func SignAceGatewayProxyAuthV2(proxyMode ProxyMode,
	proxyValue string,
	proxyScheme string,
	proxyHost string,
	method string,
	path string,
	isUnderlay bool,
	accessKey, secretKey string,
	expiration int64) (string, error) {

	signData := []byte("")

	authVersion := VersionV2
	timestamp := time.Now().Unix()
	signKeyDataSlice := make([]string, 0, 11)
	signKeyDataSlice = append(signKeyDataSlice, string(authVersion))
	signKeyDataSlice = append(signKeyDataSlice, accessKey)
	signKeyDataSlice = append(signKeyDataSlice, strconv.FormatInt(timestamp, 10))
	signKeyDataSlice = append(signKeyDataSlice, strconv.FormatInt(expiration, 10))
	signKeyDataSlice = append(signKeyDataSlice, string(proxyMode))
	signKeyDataSlice = append(signKeyDataSlice, proxyValue)
	signKeyDataSlice = append(signKeyDataSlice, proxyScheme)
	signKeyDataSlice = append(signKeyDataSlice, proxyHost)
	signKeyDataSlice = append(signKeyDataSlice, strconv.FormatBool(isUnderlay))
	signKeyDataSlice = append(signKeyDataSlice, method)
	signKeyDataSlice = append(signKeyDataSlice, path)
	signKeyData := strings.Join(signKeyDataSlice, "\n")

	signKey := sha256HMAC([]byte(secretKey), []byte(signKeyData))
	signature := sha256HMAC(signKey, signData)
	auth := fmt.Sprintf(authV2Fmt, authVersion, accessKey, timestamp, expiration, proxyMode, proxyValue, proxyScheme, proxyHost, isUnderlay, signature)
	return auth, nil
}

func SignAceGatewayProxyAuthV4(proxyMode ProxyMode,
	proxyValue string,
	proxyScheme string,
	proxyHost string,
	method string,
	isUnderlay bool,
	accessKey, secretKey string,
	expiration int64,
	tlsConf string,
	path string,
	body string) (string, error) {

	signData := []byte(body)

	authVersion := VersionV4
	timestamp := time.Now().Unix()
	signKeyDataSlice := make([]string, 0, 11)
	signKeyDataSlice = append(signKeyDataSlice, string(authVersion))
	signKeyDataSlice = append(signKeyDataSlice, accessKey)
	signKeyDataSlice = append(signKeyDataSlice, strconv.FormatInt(timestamp, 10))
	signKeyDataSlice = append(signKeyDataSlice, strconv.FormatInt(expiration, 10))
	signKeyDataSlice = append(signKeyDataSlice, string(proxyMode))
	signKeyDataSlice = append(signKeyDataSlice, proxyValue)
	signKeyDataSlice = append(signKeyDataSlice, proxyScheme)
	signKeyDataSlice = append(signKeyDataSlice, proxyHost)
	signKeyDataSlice = append(signKeyDataSlice, strconv.FormatBool(isUnderlay))
	signKeyDataSlice = append(signKeyDataSlice, method)
	signKeyDataSlice = append(signKeyDataSlice, path)
	signKeyDataSlice = append(signKeyDataSlice, tlsConf)

	signKeyData := strings.Join(signKeyDataSlice, "\n")

	signKey := sha256HMAC([]byte(secretKey), []byte(signKeyData))
	signature := sha256HMAC(signKey, signData)
	auth := fmt.Sprintf(authV4Fmt, authVersion, signature, accessKey, timestamp, expiration, proxyMode, proxyValue, proxyScheme, proxyHost, isUnderlay, tlsConf)
	return auth, nil
}

func sha256HMAC(key []byte, data []byte) []byte {
	m := hmac.New(sha256.New, key)
	m.Write(data)
	return []byte(fmt.Sprintf("%x", m.Sum(nil)))
}
